''' Utilities to create CSV spreadsheets.
    
    Copyright (C) 2013 Jerrad Michael Genson

    This program is free software: you can redistribute it and/or modify
    it under the terms of the BSD 3-Clause License as published by
    the Open Source Initiative.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    BSD 3-Clause License for more details.
    
    You should have received a copy of the BSD 3-Clause License
    along with this program.  If not, see 
    <https://betelgeuse-ml.googlecode.com/hg/LICENSE>     
    '''

# Python built-in imports
import copy
import types

# Module header
__author__ = 'Jerrad Genson'
__contact__ = 'jerradgenson@gmail.com'
__copyright__ = 'Copyright 2013 Jerrad Michael Genson'
__license__ = 'BSD 3-Clause'


class CSVSpreadsheet(object):
    '''Formats output data as a comma-separated value spreadsheet.
       @option name The name of the initial sheet.
       @option *args Any number of arbitrary values to use as a header row.
                     If first element is a list, list values will be used
                     as header row.
                     '''

    def __init__(self, name='', *args):
        # Internal representation of spreadsheet.
        self._spreadsheet = []
        # The current sheet.
        self._sheet = []
        self._spreadsheet.append(self._sheet)
        # The name of the current sheet.
        self._name = [name]
        # The number of the current sheet.
        self._number = 0
        # Add headers to spreadsheet.
        if len(args) == 0:
            # No headers
            pass

        elif isinstance(args[0], (list, tuple)):
            # Headers given as a list.
            self._sheet.append(args[0])

        else:
            # Headers given as multiple arguments.
            self._sheet.append(args)

        self._row = []
        self._sheet.append(self._row)

    def add_row(self):
        ''' Add a new row of data to the sheet.
            @return None
            '''

        self._row = []
        self._sheet.append(self._row)

    def add_sheet(self):
        ''' Add a new sheet to the spreadsheet.
            @return None
            '''

        self._sheet = []
        self.add_row()
        self._number += 1
        self._name.append('')
        self._spreadsheet.append(self._sheet)

    def _generate_sheet(self):
        ''' This method is called each time a representation of the
            sheet is output in any form.
            '''
        
        return copy.deepcopy(self._sheet)

    def __str__(self):
        '''String representation of current sheet.'''

        # Convert list matrix to a CSV spreadsheet.
        return '\n'.join([', '.join(map(str, row)) for row in self.sheet])

    def __get_name(self):
        return self._name[self._number]

    def __set_name(self, name):
        self._name[self._number] = name

    def __del_name(self):
        del self._name[self._number]

    name = property(__get_name,
                    __set_name,
                    __del_name,
                    'Name of current sheet.')

    def __get_sheet(self):
        return self._generate_sheet()

    def __set_sheet(self, index):
        self._sheet = self._spreadsheet[index]
        self._number = index
        try:
            self._row = self._sheet[0]

        except IndexError:
            pass

    def __del_sheet(self):
        del self._sheet

    sheet = property(__get_sheet,
                     __set_sheet,
                     __del_sheet,
                     'Current sheet in spreadsheet.')

    def __get_row(self):
        return self._row

    def __set_row(self, index):
        self._row = self._sheet[index]

    def __del_row(self):
        del self._row

    row = property(__get_row,
                   __set_row,
                   __del_row,
                   'Current row of the current sheet.')

class ClassifySpreadsheet(CSVSpreadsheet):
    ''' Formats output data for ANN classification as a 
        comma-separated value spreadsheet.'''

    def __init__(self, name='', *args):
        # Call superclass constructor.
        if isinstance(args[0], (list, tuple)):
            super(ClassifySpreadsheet, self).__init__(name, args[0])

        else:
            super(ClassifySpreadsheet, self).__init__(name, args)
        
        # Accumulative sum of correct classification values.
        self._accum_correct = 0.0

        # Keep track of the "value" rows added to this spreadsheet.
        # Should only include rows of numeric values; not header or footer rows.
        self._value_rows = 0

    def add_row(self):
        '''Generate row for ANN classification.'''
        # If this was a correct classification, record it.
        try:
            if self.row[-1] == self.row[-2]:
                self._accum_correct += 1

        except IndexError:
            # This will always occur on the first row.
            pass

        try:
            float(self.row[0])
            self._value_rows += 1

        except ValueError:
            # This row does not contain numeric values.
            # It is either a header or a footer - not what we want.
            pass

        except IndexError:
            # This row does not contain numeric values.
            # It is an empty row - not what we want.
            pass

        # Add row using superclass method.
        super(ClassifySpreadsheet, self).add_row()

    def _generate_sheet(self):
        '''Generate a sheet for ANN classification.'''

        # Get sheet from superclass.
        sheet = super(ClassifySpreadsheet, self)._generate_sheet()

        self.add_row()
        # Add TOTAL CORRECT row.        
        row1 = ['TOTAL CORRECT:']
        # Create empty "filler" cells.
        # Save first column for header, and last column for data.
        row1 += ['' for x in range(len(self._sheet[0]) - 2)]
        row1.append(self._accum_correct)
        sheet.append(row1)

        # Add ACCURACY row.
        row2 = ['ACCURACY:']
        row2 += ['' for x in range(len(self._sheet[0]) - 2)]
        # Compute accuracy.
        accuracy = self._accum_correct / self._value_rows
        row2.append(accuracy)
        sheet.append(row2)

        # Return sheet with additional rows appended.
        return sheet

class PredictSpreadsheet(CSVSpreadsheet):
    ''' Formats output data for ANN prediction as a 
        comma-separated value spreadsheet.'''

    def __init__(self, name='', *args):
        # Call superclass constructor.
        if isinstance(args[0], (tuple, list)):
            super(PredictSpreadsheet, self).__init__(name, args[0])

        else:
            super(PredictSpreadsheet, self).__init__(name, args)

        # Keep track of total distance between prediction and expected value.
        self._total_distance = 0.0

    def add_row(self):
        '''Generate row for ANN classification.'''
           
        # Add distance accumulated total.
        try:
            self._total_distance += abs(self.row[-1] -
                                        self.row[-2])

        except IndexError:
            # This will always occur for the first row.
            pass

        # Add row using superclass method.
        super(PredictSpreadsheet, self).add_row()

    def _generate_sheet(self):
        '''Generate a sheet for ANN prediction.'''

        # Get sheet from superclass.
        sheet = super(PredictSpreadsheet, self)._generate_sheet()

        self.add_row()
        # Add AVERAGE DISTANCE row.
        # Save first column for header, and last column for data.
        row = ['AVERAGE DISTANCE:']
        # Create empty "filler" cells.
        row += ['' for x in range(len(self._sheet[0]) - 2)]
        # Compute average distance.
        average_distance = self._total_distance / (len(self._sheet) - 1)
        row.append(average_distance)
        sheet.append(row)

        # Return sheet with additional rows appended.
        return sheet
